﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using FieldLib.DataAccess;
using FieldLib;
using FieldLib.Comm;
using System.Runtime.InteropServices;
namespace FieldDevice.ModBus
{
    /// <summary>
    /// 实现与HoneyWell系统的互联
    /// </summary>
    public class ModBusTCPServer : FieldObject
    {
        DeviceManager DeviceManager;
        public ModBusTCPServer(DeviceManager devicemanager)
        {
            this.DeviceManager = devicemanager;
        }

        /// <summary>
        /// 映射逻辑积存器到系统内设备的物理寄存器
        /// </summary>
        public class LogicalRegister
        {
            public byte LogicalModAddr;
            public ushort LogicalRegAddr;
            public int DeviceId;
            public Device Device;
            /// <summary>
            /// 度量的名字，在Device.CurrentReads中的键值            /// 
            /// </summary>
            public string QuantityName;
            public ushort PhyRegAddr;
            public uint LogKey;
        }
        public SortedList<uint, LogicalRegister> LogicalRegistersModAddressMap = new SortedList<uint, LogicalRegister>();

        /// <summary>
        /// Field.sdf数据库中的TCPLogicalDeviceMap表中记录了将各个设备映射为某个ModBus地址的某个寄存器地址
        /// </summary>
        void LoadLogicalMap()
        {
            Storage storage = new Storage();
            FieldDataSet.TCPLogicalDeviceMapDataTable devices = storage.LoadTCPModBusLogicDeviceMap();
            foreach (FieldDataSet.TCPLogicalDeviceMapRow map in devices)
            {
                LogicalRegister reg = new LogicalRegister();
                reg.LogicalModAddr = (byte)map.LogModAddr;
                reg.LogicalRegAddr = (ushort)map.LogRegAddr;
                reg.LogKey = (uint)map.LogModAddr;
                reg.LogKey <<= 16;
                reg.LogKey += (uint)map.LogRegAddr;
                Device device = this.DeviceManager.GetDevice(map.DeviceName);
                reg.QuantityName = map.Name.Trim();
                reg.Device = device;
                reg.PhyRegAddr = (ushort)map.PhyRegAddr;
                this.LogicalRegistersModAddressMap.Add(reg.LogKey, reg);
            }
        }


        Socket ServerSocket;         
        public void Start()
        {
            this.LoadLogicalMap();
            System.Threading.ThreadStart start = new System.Threading.ThreadStart(ServerThread);
            System.Threading.Thread thread = new System.Threading.Thread(start);
            thread.Name = "ModeBusTCPServerThread";
            thread.Start();
        }
        public void Stop()
        {
            this.stopped = true;
            if(this.ServerSocket.Connected)
                this.ServerSocket.Disconnect(false);
            this.ServerSocket.Close();
            for(int i=this.Clients.Count-1;i>=0;i--)
            {
                ModBusTcpClient client = this.Clients[i];
                client.Stop();
            }
        }

        public List<ModBusTcpClient> Clients = new List<ModBusTcpClient>();
        bool stopped = false;

        void ServerThread()
        {
           int port = 502;
            try
            {
                 ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                ServerSocket.Bind(new IPEndPoint(IPAddress.Any, port));
                ServerSocket.Listen(10);
            }
            catch (SocketException exp)
            {
                string message = exp.Message + exp.ErrorCode + exp.SocketErrorCode;
                switch (exp.SocketErrorCode)
                {
                    case SocketError.AddressAlreadyInUse:
                        message += "断口" + port + "已被占用!";
                        break;
                }
                base.FireFieldEvent("启动ModBusTcp Server发生异常!"+exp.SocketErrorCode, message);
                 ExceptionHandler.OnException(this, exp);
                return;
            }
            catch (Exception exp)
            {
                ExceptionHandler.OnException(this, exp);
            }
            try
            {
                while (!stopped)
                {
                    lock (ServerSocket)
                    {
                        Socket cliensocket = ServerSocket.Accept();
                        for(int i=this.Clients.Count-1;i>=0;i--)
                        {
                            ModBusTcpClient prev = this.Clients[i];
                            DismissClient(prev);
                        }
                        ModBusTcpClient client = new ModBusTcpClient(this, cliensocket);
                        this.EnrollClient(client); ;
                        client.Start();
                    }
                }
            }
            catch (SocketException exp)
            {
                switch (exp.ErrorCode)
                {
                    case 0x2714://主动关闭
                        break;
                    default:
                        ExceptionHandler.OnException(this, exp);
                        break;
                }
            }
        }

        public enum ClientEventType
        {
            Add,
            Remove,
        }
        public delegate void ClientEventHandler(ModBusTcpClient client,  ClientEventType eventtype);
        public event ClientEventHandler ClientEvent;

        void EnrollClient(ModBusTcpClient client)
        {
            FieldLib.FieldSystem.g_FieldSystem.EnrollModBusTcpClient(client);
            this.Clients.Add(client);
        }

        void DismissClient(ModBusTcpClient client)
        {
            if (client.dismissed)
                return;
            client.dismissed = true;//reentrance avoiding
            client.Stop();
            this.Clients.Remove(client);
            FieldLib.FieldSystem.g_FieldSystem.DismissModBusTcpClient(client);
        }
        public class ModBusTcpClient : FieldLib.Comm.IChannel
        {
            public ModBusTcpClient(ModBusTCPServer server, Socket clientsocket)
            {
                this.ModBusTCPServer = server;
                this.clientsocket = clientsocket;
            }

            public void Start()
            {
                System.Threading.ParameterizedThreadStart start = new System.Threading.ParameterizedThreadStart(this.ClientThread);
                this.thread = new System.Threading.Thread(start);
                this.thread.Name = "ModBusTCPClientThread";
                this.thread.Start(clientsocket);
            }

            public bool dismissed;
            public void Stop()
            {
                if (this.stopped)
                    return;
                this.stopped = true;
                try
                {
                    if (this.clientsocket.Connected)
                    {
                        this.clientsocket.Disconnect(false);
                    }
                    this.clientsocket.Close();
                }
                catch (SocketException exp)
                {
                    //do nothing
                }

                if (this.thread.ThreadState == System.Threading.ThreadState.Running)
                    this.thread.Abort();
            }

            ModBusTCPServer ModBusTCPServer;
            Socket clientsocket;
            System.Threading.Thread thread;
            bool stopped = false;
            ChannelCategory IChannel.Category { get { return ChannelCategory.ModBusTCPClient; } }
            List<byte[]> outlogs = new List<byte[]>();
            IList<byte[]> IChannel.OutLogs { get { return outlogs; } }
            List<byte[]> inlogs = new List<byte[]>();
            IList<byte[]> IChannel.InLogs { get { return inlogs; } }
            ICommWatch Watch;

            void Log(CommDirection dir, byte[] data, int begin, int length)
            {
                byte[] bs = new byte[length];
                for (int i = 0; i < length; i++)
                {
                    bs[i] = data[begin + i];
                }
                Log(dir, bs);
            }
            void Log(CommDirection dir, byte[] data)
            {
                if (this.Watch != null)
                    this.Watch.Watch(dir, data);
                if (dir == CommDirection.In)
                {
                    this.inlogs.Add(data);
                    if (this.inlogs.Count > 100)
                        this.inlogs.RemoveAt(0);
                }
                else
                {
                    this.outlogs.Add(data);
                    if (this.outlogs.Count > 100)
                        this.outlogs.RemoveAt(0);
                }
            }
            public void AddWatch(ICommWatch watch)
            {
                this.Watch = watch;
            }

            public void RemoveWatch(ICommWatch watch)
            {
                this.Watch = null;
            }

            void ClientThread(object s)
            {
                Socket clientsocket = (Socket)s;
                byte[] buffer = new byte[1024];
                while (clientsocket.Connected && !stopped)
                {
                    try
                    {
                        int length = clientsocket.Receive(buffer);
                        if (length > 0)
                        {
                            this.Log(CommDirection.In, buffer, 0, length);
                            lock (this)
                            {
                                byte[] result = this.ModBusTCPServer.OnModBusPacket(buffer, length);
                                if (result != null)
                                {
                                    clientsocket.Send(result);
                                    string r = "out:" + Utility.Bytes2String(result);
                                    System.Diagnostics.Trace.WriteLine(r);
                                    Log(CommDirection.Out, result);
                                }
                            }
                        }
                        else
                            System.Threading.Thread.Sleep(10);
                    }
                    catch (SocketException exp)
                    {
                        switch (exp.ErrorCode)
                        {
                            case 10053://本地关闭连接
                            case 10054://远程关闭连接
                                break;
                            default:
                                FieldLib.ExceptionHandler.OnException(this, exp);
                                break;
                        }
                    }
                    finally
                    {
                    }
                }//end while
                try
                {
                    this.ModBusTCPServer.DismissClient(this);
                }
                catch (Exception exp)
                {
                    FieldLib.ExceptionHandler.OnException(this, exp);
                }


            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="length"></param>
        public byte[] OnModBusPacket(byte[] buffer, int length)
        {
            int start = 0;
            string r = Utility.Bytes2String(buffer, 0, length);
            System.Diagnostics.Trace.WriteLine(r);
            if (length == 12)
            {
                byte modeaddress = buffer[6];
                System.Diagnostics.Trace.Write(buffer[0].ToString("X") + buffer[1].ToString("X") + " ");
                short regaddress = StaticLibrary.Bytes2Short(buffer[8], buffer[9]);
                short reglen = StaticLibrary.Bytes2Short(buffer[10], buffer[11]);
                if ((reglen > 126) || ((reglen % 2) == 1))
                    reglen = 2;//??忽略长度,总是回复
                uint logkey = modeaddress;
                logkey <<= 16;
                logkey += (uint)regaddress;
                byte[] result = new byte[reglen * 2 + 3 + 6];
                for (int i = 0; i < 8; i++)
                    result[i] = buffer[i];
                result[5] = (byte)(3 + reglen * 2);
                result[8] = (byte)(reglen * 2);
                int begin = 9;
                for (uint i = 0; i < reglen; i += 2)
                {//对每个寄存器进行查找
                    LogicalRegister reg;
                    if (this.LogicalRegistersModAddressMap.TryGetValue(logkey, out reg))
                    {
                        float floatValue;
                        if (reg.Device.CurrentReads.TryGetValue(reg.QuantityName, out floatValue))
                        {
                            StaticLibrary.FloatToBytes(floatValue, result, begin);
                        }
                        //for (int i = 0; i < reglen; i++)
                        //{
                        //    ushort phyreg = (ushort)(reg.PhyRegAddr + i);
                        //    ushort value;
                        //    if (reg.Device.Registers.TryGetValue(phyreg, out value))
                        //    {
                        //        result[begin + i * 2 + 1] = (byte)value;
                        //        result[begin + i * 2] = (byte)(value >> 8);
                        //    }
                        //    else
                        //    {
                        //        result[begin + i * 2 + 1] = 0;
                        //        result[begin + i * 2] = 0;
                        //    }
                        //}
                        //result[0] = reg.Device.ModbusAddress;
                        //result[1] = reg.Device.Requests[reg.Device.ModbusAddress].Function;
                        //result[2] = (byte)(reglen*2);
                        //CRC.CRC16(result, result.Length - 2, out result[reglen*2+4], out result[reglen*2+3]);
                    }
                    logkey += 2;
                    begin += 4;
                }
                return result;

            }
            return null;
        }

    }
}
